package org.springframework.beanPostProcessor;

import org.springframework.beanPostProcessor.entity.BeanPostProcessorBean1;
import org.springframework.beanPostProcessor.entity.BeanPostProcessorBean2;
import org.springframework.beanPostProcessor.entity.BeanPostProcessorBean3;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.StandardEnvironment;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: T-jack
 * @Date: 2024-01-09-10:18
 * @Description: 这个类用作学习常见的后处理器的学习 此处主要学习 AutowiredAnnotationBeanPostProcessor
 *
 */
public class BeanPostProcessorMain {


	/**
	 * 基础调用过程
	 */
	public void base() {
		try {
			DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
			// 这里为了省事就不使用 beanFactory.registerBeanDefinition()方法去添加类的描述信息了
			// 直接使用 beanFactory.registerSingleton可以直接将Bean的单例对象注入进去，
			// 后面调用beanFactory.getBean()方法的时候就不会去根据Bean的定义去创建Bean的实例了，
			// 也不会有懒加载和依赖注入的初始化过程了。
			// 使用此方法视为成品bean
			beanFactory.registerSingleton("bean2", new BeanPostProcessorBean2());
			beanFactory.registerSingleton("bean3", new BeanPostProcessorBean3());

			// 设置@Autowired注解的解析器 这个解析器不加解析@Value会报错
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			// ${} 内嵌池解析器
			beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
			// 创建后处理器
			AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
			// 后处理器在解析@Autowired和@Value的时候需要用到其他Bean，
			// 而BeanFactory提供了需要的Bean，所以需要把BeanFactory传给这个后处理器
			processor.setBeanFactory(beanFactory);
			BeanPostProcessorBean1 bean1 = new BeanPostProcessorBean1();
			 System.out.println(bean1);
			// 解析@Autowired和@Value注解，执行依赖注入
			// PropertyValues pvs: 给注解的属性注入给定的值，这里不需要手动给定，传null即可
			 processor.postProcessProperties(null, bean1, "bean1");
			// 重新输出bean1 此处已经执行依赖注入
			 System.out.println(bean1);
			// postProcessProperties()方法底层原理探究
			// 通过查看源码得知 postProcessProperties()方法中调用了一个私有的方法findAutowiringMetadata(beanName, bean.getClass(), pvs); 会返回一个InjectionMetadata的对象，然后会调用InjectionMetadata.inject(bean1, "bean1", null)进行依赖注入
			// 通过反射调用一下
			Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
			findAutowiringMetadata.setAccessible(true);
			// 获取Bean1上加了@Value @Autowired注解的成员变量和方法参数信息
			InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", BeanPostProcessorBean1.class, null);
			System.out.println(metadata);
			metadata.inject(bean1, "bean1", null);
			System.out.println(bean1);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * AutowiredAnnotationBeanPostProcessor 后处理器中的两个步骤
	 *  解析分为两步
	 * 	  1.找到指定注解属性及方法
	 * 	  2.对指定属性进行属性注入
	 */
	public void getMethod(){
		try {
			DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
			// 这里为了省事就不使用 beanFactory.registerBeanDefinition()方法去添加类的描述信息了
			// 直接使用 beanFactory.registerSingleton可以直接将Bean的单例对象注入进去，
			// 后面调用beanFactory.getBean()方法的时候就不会去根据Bean的定义去创建Bean的实例了，
			// 也不会有懒加载和依赖注入的初始化过程了。
			// 使用此方法视为成品bean
			beanFactory.registerSingleton("bean2", new BeanPostProcessorBean2());
			beanFactory.registerSingleton("bean3", new BeanPostProcessorBean3());

			// 设置@Autowired注解的解析器 这个解析器不加解析@Value会报错
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			// ${} 内嵌池解析器
			beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);
			// 创建后处理器
			AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
			// 后处理器在解析@Autowired和@Value的时候需要用到其他Bean，
			// 而BeanFactory提供了需要的Bean，所以需要把BeanFactory传给这个后处理器
			processor.setBeanFactory(beanFactory);
			BeanPostProcessorBean1 bean1 = new BeanPostProcessorBean1();
			System.out.println(bean1);
			// postProcessProperties()方法底层原理探究
			// 通过查看源码得知 postProcessProperties()方法中调用了一个私有的方法findAutowiringMetadata(beanName, bean.getClass(), pvs); 会返回一个InjectionMetadata的对象，然后会调用InjectionMetadata.inject(bean1, "bean1", null)进行依赖注入
			// 通过反射调用一下
			Method findAutowiringMetadata = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
			findAutowiringMetadata.setAccessible(true);
			// 获取Bean1上加了@Value @Autowired注解的成员变量和方法参数信息
			InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", BeanPostProcessorBean1.class, null);
			System.out.println(metadata);
			metadata.inject(bean1, "bean1", null);
			System.out.println(bean1);
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 根据属性上的注解标记对对应属性赋值
	 * 此处是inject方法内部的大致实现
	 * 1.通过获取到的属性 方法 得到需要注入的参数类型
	 * 2.通过类型在beanFactory中找到对应对象注入
	 */
	public void getValue(){
		try {
			DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
			// 这里为了省事就不使用 beanFactory.registerBeanDefinition()方法去添加类的描述信息了
			// 直接使用 beanFactory.registerSingleton可以直接将Bean的单例对象注入进去，
			// 后面调用beanFactory.getBean()方法的时候就不会去根据Bean的定义去创建Bean的实例了，
			// 也不会有懒加载和依赖注入的初始化过程了。
			beanFactory.registerSingleton("bean2", new BeanPostProcessorBean2());
			beanFactory.registerSingleton("bean3", new BeanPostProcessorBean3());
			// 设置@Autowired注解的解析器
			beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			// 设置解析 @Value 注解中的 ${} 表达式的解析器
			beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

			// 1. 查找哪些属性、方法加了 @Autowired，这称之为InjectionMetadata
			// 创建后处理器
			AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
			// 后处理器在解析@Autowired和@Value的时候需要用到其他Bean，
			// 而BeanFactory提供了需要的Bean，所以需要把BeanFactory传给这个后处理器
			processor.setBeanFactory(beanFactory);
			// 创建Bean1
			BeanPostProcessorBean1 bean1 = new BeanPostProcessorBean1();
			System.out.println(bean1);


			// 3. 如何去Bean工厂里面按类型查找值
			// 由于InjectionMetadata.inject(bean1, "bean1", null)的源码调用链过长，摘出主要调用过程进行演示

			// 3.1 @Autowired加在成员变量上，InjectionMetatadata给Bean1注入Bean3的过程
			// 通过InjectionMetadata把Bean1加了@Autowired注解的属性的BeanName先拿到，这里假设拿到的BeanName就是 bean3
			// 通过BeanName反射获取到这个属性，
			Field bean3Field = BeanPostProcessorBean1.class.getDeclaredField("bean3");
			// 设置私有属性可以被访问
			bean3Field.setAccessible(true);
			// 将这个属性封装成一个DependencyDescriptor对象
			DependencyDescriptor dd1 = new DependencyDescriptor(bean3Field, false);
			// 再执行beanFactory的doResolveDependency
			//根据成员变量信息获取类型 再根据类型在beanFactory中找到对应的bean
			BeanPostProcessorBean3 bean3Value = (BeanPostProcessorBean3) beanFactory.doResolveDependency(dd1, null, null, null);
			System.out.println(bean3Value);
			// 给Bean1的成员bean3赋值
			bean3Field.set(bean1, bean3Value);
			System.out.println(bean1);

			// 3.2 @Autowired加在方法上，InjectionMetatadata给Bean1注入Bean2的过程
			Method setBean2 = BeanPostProcessorBean2.class.getDeclaredMethod("setBean2", BeanPostProcessorBean2.class);
			DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
			BeanPostProcessorBean2 bean2Value = (BeanPostProcessorBean2) beanFactory.doResolveDependency(dd2, "bean2", null, null);
			System.out.println(bean2Value);
			// 给Bean1的setBean2()方法的参数赋值
			setBean2.invoke(bean1, bean2Value);
			System.out.println(bean1);


			// 3.3 @Autowired加在方法上，方法参数为String类型，加了@Value，
			// InjectionMetadata给Bean1注入环境变量JAVA_HOME属性的值
			Method setJava_home = BeanPostProcessorBean1.class.getDeclaredMethod("setJava_home", String.class);
			DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setJava_home, 0), true);
			String java_home = (String) beanFactory.doResolveDependency(dd3, null, null, null);
			System.out.println(java_home);
			setJava_home.invoke(bean1, java_home);
			System.out.println(bean1);


		} catch (NoSuchFieldException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}


}
